// CS3241Lab1.cpp : Defines the entry point for the console application.
#include <cmath>
#include <iostream>
#include <time.h>
#include <string.h>

/* header files for xcode */
#include <OpenGL/gl.h>
#include <GLUT/GLUT.h>

/* header files for lab machine */
//#include "GL/glut.h"

using namespace std;

#define numStars 100
#define numPlanets 9
#define EPS 1e-3
#define gapCycle 100

class planet
{
public:
	float distFromRef;
	float angularSpeed;
	GLfloat color1[4];
	GLfloat color2[4];
	float size;
	float angle;
    
	planet()
	{
		distFromRef = 0;
		angularSpeed = 0;
		memset(color1, 0, sizeof(color1));
		memset(color2, 0, sizeof(color2));
		size = 0;
		angle = 0;
	}
};

class icon
{
public:
    float H;
    
    float v;
    float h;
    float size;
    float g;
    float t;
    float horizon;
    
    float deltaT;
    
    bool fall;
    
    GLfloat color[4];
    
    icon()
    {
        H = 3;
        deltaT = 0.001;
        v = 0;
        h = H;
        g = 20.8;
        size = 0.2;
        color[0] = color[1] = color[2] = color[3] = 1;
        
        fall = true;
        t = 0;
    }
    
    void update()
    {
        if (fall)
        {
            if (h > 0)
            {
                h = H - 0.5 * g * t * t;
                v = g * t;
            } else
            if (h < horizon)
            {
                fall = false;
                t = 0;
            }
        } else
        {
            if (h < H)
            {
                h = v * t - 0.5 * g * t * t;
            } else
            {
                fall = true;
                t = 0;
            }
        }
        
        t += deltaT;
    }
};

GLfloat PI = 3.14;
float alpha = 0.0, k=1;
float tx = 0.0, ty=0.0;
planet planetList[numPlanets], commet;
icon obj;
int flag = 0;
int tickFillInStatus = 1;
float oldAS[5];
bool ticked = false;
int tickCnt;
float x[numStars + 5], y[numStars + 5], s[numStars + 5], al[numStars + 5], l[numStars + 5];

void reshape (int w, int h)
{
	glViewport (0, 0, (GLsizei) w, (GLsizei) h);
    
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity();
    
	glOrtho(-10, 10, -10, 10, -10, 10);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void init(void)
{
	glClearColor (0.0, 0.0, 0.3, 1.0);
	glShadeModel (GL_SMOOTH);
	glEnable(GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void setPlanets()
{
	planetList[0].angle = -10;
	planetList[0].distFromRef = 2.5;
	planetList[0].size = 0.6;
	planetList[0].angularSpeed = 0.02;
	planetList[0].color1[0] = 0.82;
	planetList[0].color1[1] = 0.41;
	planetList[0].color1[2] = 0.13;
	planetList[0].color2[0] = 164.0 / 255.0;
	planetList[0].color2[1] = 85.0 / 255.0;
	planetList[0].color2[2] = 25.0 / 255.0;
    
	planetList[1].angle = 60;
	planetList[1].distFromRef = 3.9;
	planetList[1].size = 0.55;
	planetList[1].angularSpeed = 0.07;
	planetList[1].color1[0] = 0.58;
	planetList[1].color1[1] = 0.44;
	planetList[1].color1[2] = 0.86;
	planetList[1].color2[0] = 99.0 / 255.0;
	planetList[1].color2[1] = 76.0 / 255.0;
	planetList[1].color2[2] = 146.0 / 255.0;
    
	planetList[2].angle = 30;
	planetList[2].distFromRef = 5.6;
	planetList[2].size = 0.5;
	planetList[2].angularSpeed = 0.05;
	planetList[2].color1[0] = 0.68;
	planetList[2].color1[1] = 1;
	planetList[2].color1[2] = 0.18;
	planetList[2].color2[0] = 123.0 / 255.0;
	planetList[2].color2[1] = 182.0 / 255.0;
	planetList[2].color2[2] = 33.0 / 255.0;
    
	planetList[3].angle = 105;
	planetList[3].distFromRef = 6.7;
	planetList[3].size = 0.4;
	planetList[3].angularSpeed = 0.02;
	planetList[3].color1[0] = 0.49;
	planetList[3].color1[1] = 0.75;
	planetList[3].color1[2] = 0.93;
	planetList[3].color2[0] = 97.0 / 255.0;
	planetList[3].color2[1] = 146.0 / 255.0;
	planetList[3].color2[2] = 183.0 / 255.0;
    
	planetList[4].angle = 200;
	planetList[4].distFromRef = 7.6;
	planetList[4].size = 0.3;
	planetList[4].angularSpeed = 0.04;
	planetList[4].color1[0] = 0.93;
	planetList[4].color1[1] = 0.47;
	planetList[4].color1[2] = 0.26;
	planetList[4].color2[0] = 171.0 / 255.0;
	planetList[4].color2[1] = 89.0 / 255.0;
	planetList[4].color2[2] = 48.0 / 255.0;
    
	planetList[5].angle = 250;
	planetList[5].distFromRef = 8.4;
	planetList[5].size = 0.3;
	planetList[5].angularSpeed = 0.06;
	planetList[5].color1[0] = 0.46;
	planetList[5].color1[1] = 0.93;
	planetList[5].color1[2] = 0.0;
	planetList[5].color2[0] = 90.0 / 255.0;
	planetList[5].color2[1] = 179.0 / 255.0;
	planetList[5].color2[2] = 0.0;
    
	planetList[6].angle = 15;
	planetList[6].distFromRef = 0.7;
	planetList[6].size = 0.15;
	planetList[6].angularSpeed = 0.02;
	planetList[6].color1[0] = 0.55;
	planetList[6].color1[1] = 0.51;
	planetList[6].color1[2] = 0.53;
	planetList[6].color2[0] = 0.55;
	planetList[6].color2[1] = 0.51;
	planetList[6].color2[2] = 0.53;
    
	planetList[7].angle = 15;
	planetList[7].distFromRef = 0.5;
	planetList[7].size = 0.15;
	planetList[7].angularSpeed = 0.02;
	planetList[7].color1[0] = 0.55;
	planetList[7].color1[1] = 0.51;
	planetList[7].color1[2] = 0.53;
    planetList[7].color2[0] = 0.55;
	planetList[7].color2[1] = 0.51;
	planetList[7].color2[2] = 0.53;

	planetList[8].angle = 60;
	planetList[8].distFromRef = 0.5;
	planetList[8].size = 0.1;
	planetList[8].angularSpeed = 0.04;
	planetList[8].color1[0] = 0.55;
	planetList[8].color1[1] = 0.51;
	planetList[8].color1[2] = 0.53;
	planetList[8].color2[0] = 0.55;
	planetList[8].color2[1] = 0.51;
	planetList[8].color2[2] = 0.53;
    
	for(int i = 0; i < numPlanets; i++)
		planetList[i].color1[3] = planetList[i].color2[3] = 1;
}

void setStars()
{
	srand(time(NULL));
	int sign;
	for(int i = 0; i < numStars; i++)
	{
		x[i] = (rand() % 100) / 10.0;
		y[i] = (rand() % 100) / 10.0;
		s[i] = (float)(rand() % 5) / 50.0;
		al[i] = (rand() % 10) / 10.0;
		sign = rand() % 2;
		if(sign == 0) sign--;
		x[i] *= sign;
		sign = rand() % 2;
		if(sign == 0) sign--;
		y[i] *= sign;
        
		if(al[i] > 0.5) l[i] = 1;
		else l[i] = 0;
	}
}

void setCommet()
{
    commet.angle = 0;
    commet.angularSpeed = 0.05;
    commet.distFromRef = 0;
	commet.size = 0.2;
	commet.color2[0] = 160.0 / 255.0;
	commet.color2[1] = 160.0 / 255.0;
	commet.color2[2] = 160.0 / 255.0;
    commet.color1[0] = 255.0 / 255.0;
	commet.color1[1] = 255.0 / 255.0;
	commet.color1[2] = 255.0 / 255.0;

    commet.color1[3] = commet.color2[3] = 1;
}

void drawEllipse(float a, float b, int num_segment,GLenum mode, icon ic){
	float x,y;
	x = -a;
	y = 0.0;
	
	glBegin( mode );
	for(int i = 0; i < num_segment; i++){
		
		glColor4f(ic.color[0], ic.color[1], ic.color[2], ic.color[3]);	
		glVertex2f(x, y);
		x = x + 2 * a / num_segment;
		y = sqrt((1 - x * x / (a * a)) * b * b);
	}
	y = 0;
	for(int i = 0; i < num_segment; i++){
		glVertex2f(x, y);
		x = x - 2 * a / num_segment;
		y = -sqrt((1 - x * x / (a * a)) * b * b);
	}
	glEnd();
}

void drawSun(float r)
{
	glColor3f(1, 1, 0);
	glBegin(GL_TRIANGLE_FAN);
	glVertex3f(0, 0, 0);
	for(int i = 0; i < 360; i++)
	{
		glColor3f(1, 140.0 / 255.0, 0);
		glVertex3f(r * sin((float)i * 2 * PI/ 360 ), r * cos((float)i * 2 * PI / 360 ), 0);
	}
	glVertex3f(0, r, 0);
	glEnd();
}

void drawPlanet(planet p)
{
    glRotatef(-p.angle, 0, 0, 1);
	glTranslatef(0, p.distFromRef, 0);
	glBegin(GL_TRIANGLE_FAN);
	glColor4f(p.color1[0], p.color1[1], p.color1[2], p.color1[3]);
	glVertex3f(0, 0, 0);
	for(int i = 0; i < 360; i++)
	{
		if(i > 90 && i < 270)
		{
			glColor4f(p.color1[0], p.color1[1], p.color1[2], p.color1[3]);
		} else
		{
			glColor4f(p.color2[0], p.color2[1], p.color2[2], p.color2[3]);
		}
		
		glVertex3f(p.size * sin((float)i * 2 * PI/ 360 ), p.size * cos((float)i * 2 * PI / 360 ), 0);

	}
	glVertex3f(0, p.size, 0);
	glEnd();
}

void drawStars()
{
	for(int i = 0; i < numStars; i++)
	{
		glPushMatrix();
		glTranslatef(x[i], y[i], 0);
		glColor4f(1, 1, 1, al[i]);
		glBegin(GL_POLYGON);
		glVertex3f(0, 0, 0);
		glVertex3f(s[i], 0, 0);
		glVertex3f(s[i], s[i], 0);
		glVertex3f(0, s[i], 0);
		glEnd();
		glPopMatrix();
	}
}

void drawCommet(int a, int b)
{
    glPushMatrix();
    
    GLfloat x = (GLfloat)a * sin(commet.angle * 2 * PI / 360.0);
    GLfloat y = (GLfloat)b * cos(commet.angle * 2 * PI / 360.0);

    glTranslatef(x, y, 0); 
    drawPlanet(commet);
    
    glPopMatrix();
}

void drawIcon()
{
    glPushMatrix();
    
    glTranslatef(0, obj.h, 0);
    
    drawEllipse(obj.size, obj.size, 180, GL_POLYGON, obj);
    
    glPopMatrix();
}

//ANS is minor angle between a and b
//return ANS if a rotate ANS degree in CW
//return -ANS if a rotate ANS degree in CCW
float calMinorA(float a, float b)
{
    float ANS = 0;
    
    //a rotate ANS, CW to reach b
    if (a < b && b - a < 180)
    {
        ANS = b - a;
    } else
    //a rotate ANS, CCW to reach b
    if (a < b && b - a >= 180)
    {
        ANS = 360 - b + a;
        ANS = -ANS;
    } else
    //a rotate ANS, CCW to reach b
    if (a > b && a - b < 180)
    {
        ANS = b - a;
    } else
    //a rotate ANS, CW to reach b
    if (a > b && a - b >= 180)
    {
        ANS = 360 - a + b;
    }
    
    return ANS;
}

void updateTickAngSpeed(planet& hour, planet& min, planet& sec, float hourA, float minA, float secA)
{
    float diffH = calMinorA(hour.angle, hourA);
    float diffM = calMinorA(min.angle, minA);
    float diffS = calMinorA(sec.angle, secA);
    /*
    printf("curA = %.2lf hourA = %.2lf\n diff = %.2lf\n", hour.angle, hourA, diffH);
    printf("curA = %.2lf minA = %.2lf\n diff = %.2lf\n", min.angle, minA, diffM);
    printf("curA = %.2lf secA = %.2lf\n diff = %.2lf\n", sec.angle, secA, diffS);
    */
    hour.angularSpeed = diffH / gapCycle;
    min.angularSpeed = diffM / gapCycle;
    sec.angularSpeed = diffS / gapCycle;
    
//    printf("hAS = %.2lf\n", hour.angularSpeed);
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glPushMatrix();
    
	//controls transformation
	glScalef(k, k, k);
	glTranslatef(tx, ty, 0);
	glRotatef(alpha, 0, 0, 1);
    
	//draw stuff here!
	drawStars();
	drawSun(1.5);
    drawCommet(5, 3);
	
	for(int i = 0; i < 6; i++)
	{
		glPushMatrix();
		drawPlanet(planetList[i]);
        if(i == 1)
        {
            glPushMatrix();
            glTranslatef(0, planetList[1].size + obj.size, 0);
            drawIcon();
            glPopMatrix();
        }
		if(i == 2) drawPlanet(planetList[6]);
		if(i == 4)
		{
			glPushMatrix();
			drawPlanet(planetList[7]);
			glPopMatrix();
			glPushMatrix();
			drawPlanet(planetList[8]);
			glPopMatrix();
		}
		glPopMatrix();
	}
	glPopMatrix();
	glFlush ();
}

void idle()
{
	//update animation here
	if(!flag)
	{
        tickFillInStatus = 1;
        
        if (ticked)
        {
            for (int i = 3; i <= 5; i ++)
            {
                planetList[i].angularSpeed = oldAS[i - 3];
            }
            
            ticked = false;
        }
        
		for(int i = 0; i < numPlanets; i++)
		{
			planetList[i].angle += planetList[i].angularSpeed;
            if (planetList[i].angle > 360) planetList[i].angle -= 360;
			if(planetList[i].color1[3] < 1)	planetList[i].color1[3] += 0.005;
			if(planetList[i].color2[3] < 1)	planetList[i].color2[3] += 0.005;
		}

		if(commet.color1[3] < 1)	commet.color1[3] += 0.005;
		if(commet.color2[3] < 1)	commet.color2[3] += 0.005;
        
        commet.angle += commet.angularSpeed;
        
        if (commet.angle > 360) commet.angle -= 360;

		if(obj.color[3] < 1)	obj.color[3] += 0.005;
        
        obj.update();
	} else
	{
		time_t t = time(NULL);
		struct tm* current_t = localtime(&t);
        
        float hour_ang = ((float)(current_t->tm_hour % 12) / 12) * 360 + (float)(current_t->tm_min) / 60 * 30;
        float min_ang = ((float)(current_t->tm_min) / 60) * 360;
        float sec_ang = ((float)(current_t->tm_sec) / 60) * 360;
        
        switch (tickFillInStatus)
        {
            case 1:
                for (int i = 3; i <= 5; i ++)
                {
                    oldAS[i - 3] = planetList[i].angularSpeed;
                }
                
                ticked = true;
                tickCnt = gapCycle;
                
                updateTickAngSpeed(planetList[3], planetList[4], planetList[5],hour_ang, min_ang, sec_ang);
                tickFillInStatus = 2;
            case 2:
                
                for (int i = 3; i <= 5; i ++)
                {
                    planetList[i].angle += planetList[i].angularSpeed;
                    
                    if (planetList[i].angle > 360)
                    {
                        planetList[i].angle -= 360;
                    } else
                    if (planetList[i].angle < 0)
                    {
                        planetList[i].angle += 360;
                    }
                }
                
//                printf("tickCnt = %d curHA = %.2lf\n", tickCnt, planetList[3].angle);
                
                
                if (!tickCnt)
                {
                    tickFillInStatus = 3;
                }
                
                tickCnt --;
                
                break;
            case 3:
                planetList[3].angle = hour_ang;
                planetList[4].angle = min_ang;
                planetList[5].angle = sec_ang;

                break;
        }
        
		planetList[7].angle += planetList[7].angularSpeed;
		planetList[8].angle += planetList[8].angularSpeed;
        
		for(int i = 0; i < numPlanets; i++)
		{
			if((i != 3 && i != 4 && i != 5 && i != 7 && i != 8) && planetList[i].color1[3] > 0)
				planetList[i].color1[3] -= 0.005;
			if((i != 3 && i != 4 && i != 5 && i != 7 && i != 8) && planetList[i].color2[3] > 0)
				planetList[i].color2[3] -= 0.005;
		}

		if (commet.color1[3] > 0) commet.color1[3] -= 0.005;
        if (commet.color2[3] > 0) commet.color2[3] -= 0.005;
		if (obj.color[3] > 0) obj.color[3] -= 0.005;
	}
    
	for(int i = 0; i < numStars; i++)
    {
        if(l[i])
        {
            al[i] += 0.001;
            if(al[i] > 1)
            {
                al[i] = 1;
                l[i] = 0;
            }
        } else
        {
            al[i] -=0.001;
            if(al[i] < 0)
            {
                al[i] = 0;
                l[i] = 1;
            }
        }
    }
    
	glutPostRedisplay();	//after updating, draw the screen again
}

void keyboard (unsigned char key, int x, int y)
{
	//keys to control scaling - k
	//keys to control rotation - alpha
	//keys to control translation - tx, ty
	switch (key) {
            
		case 'a':
			alpha+=10;
			glutPostRedisplay();
            break;
            
		case 'd':
			alpha-=10;
			glutPostRedisplay();
            break;
            
		case 'q':
			k+=0.1;
			glutPostRedisplay();
            break;
            
		case 'e':
			if(k>0.1)
				k-=0.1;
			glutPostRedisplay();
            break;
            
		case 'z':
			tx-=0.1;
			glutPostRedisplay();
            break;
            
		case 'c':
			tx+=0.1;
			glutPostRedisplay();
            break;
            
		case 's':
			ty-=0.1;
			glutPostRedisplay();
            break;
            
		case 'w':
			ty+=0.1;
			glutPostRedisplay();
            break;
            
		case 't':
			if(flag == 0)	flag++;
			else	flag--;
            break;
            
		default:
            break;
	}
}

int main(int argc, char **argv)
{
	cout<<"CS3241 Lab 2\n\n";
	cout<<"+++++CONTROL BUTTONS+++++++\n\n";
	cout<<"Scale Up/Down: Q/E\n";
	cout<<"Rotate Clockwise/Counter-clockwise: A/D\n";
	cout<<"Move Up/Down: W/S\n";
	cout<<"Move Left/Right: Z/C\n";
    
	glutInit(&argc, argv);
	glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize (600, 600);
	glutInitWindowPosition (50, 50);
	glutCreateWindow (argv[0]);
	init ();
	setStars();
    setCommet();
	setPlanets();
	glutDisplayFunc(display);
	glutIdleFunc(idle);
	glutReshapeFunc(reshape);	
	//glutMouseFunc(mouse);
	glutKeyboardFunc(keyboard);
	glutMainLoop();
    
	return 0;
}